/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.phoenix.util;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;

import org.apache.phoenix.thirdparty.com.google.common.collect.Iterables;

/**
 * Utilities for operating on {@link SQLCloseable}s.
 * @since 0.1
 */
public class SQLCloseables {
  /** Not constructed */
  private SQLCloseables() {
  }

  /**
   * Allows you to close as many of the {@link SQLCloseable}s as possible. If any of the close's
   * fail with an IOException, those exception(s) will be thrown after attempting to close all of
   * the inputs.
   */
  public static void closeAll(Iterable<? extends SQLCloseable> iterable) throws SQLException {
    SQLException ex = closeAllQuietly(iterable);
    if (ex != null) throw ex;
  }

  public static SQLException closeAllQuietly(Iterable<? extends SQLCloseable> iterable) {
    if (iterable == null) return null;

    LinkedList<SQLException> exceptions = null;
    for (SQLCloseable closeable : iterable) {
      try {
        if (closeable == null) {
          continue;
        }
        closeable.close();
      } catch (SQLException x) {
        if (exceptions == null) exceptions = new LinkedList<SQLException>();
        exceptions.add(x);
      }
    }

    SQLException ex = MultipleCausesSQLException.fromSQLExceptions(exceptions);
    return ex;
  }

  /**
   * A subclass of {@link SQLException} that allows you to chain multiple causes together.
   * @since 0.1
   * @see SQLCloseables
   */
  static private class MultipleCausesSQLException extends SQLException {
    private static final long serialVersionUID = 1L;

    static SQLException fromSQLExceptions(Collection<? extends SQLException> exceptions) {
      if (exceptions == null || exceptions.isEmpty()) return null;
      if (exceptions.size() == 1) return Iterables.getOnlyElement(exceptions);

      return new MultipleCausesSQLException(exceptions);
    }

    private final Collection<? extends SQLException> exceptions;
    private boolean hasSetStackTrace;

    /**
     * Use the {@link #fromSQLExceptions(Collection) factory}.
     */
    private MultipleCausesSQLException(Collection<? extends SQLException> exceptions) {
      this.exceptions = exceptions;
    }

    @Override
    public String getMessage() {
      StringBuilder sb = new StringBuilder(this.exceptions.size() * 50);
      int exceptionNum = 0;
      for (SQLException ex : this.exceptions) {
        sb.append("Cause Number " + exceptionNum + ": " + ex.getMessage() + "\n");
        exceptionNum++;
      }
      return sb.toString();
    }

    @Override
    public StackTraceElement[] getStackTrace() {
      if (!this.hasSetStackTrace) {
        ArrayList<StackTraceElement> frames =
          new ArrayList<StackTraceElement>(this.exceptions.size() * 20);

        int exceptionNum = 0;
        for (SQLException exception : this.exceptions) {
          StackTraceElement header =
            new StackTraceElement(MultipleCausesSQLException.class.getName(),
              "Exception Number " + exceptionNum, "<no file>", 0);

          frames.add(header);
          Collections.addAll(frames, exception.getStackTrace());
          exceptionNum++;
        }

        setStackTrace(frames.toArray(new StackTraceElement[frames.size()]));
        this.hasSetStackTrace = true;
      }

      return super.getStackTrace();
    }

  }

}
